#include "ModuleRegistry.h"

#include <assert.h>

#include <optional>
#include <stdexcept>

#include "NativeModule.h"

namespace jsiexecutor {
// add new modules
void ModuleRegistry::registerModules(std::vector<std::unique_ptr<NativeModule>> modules) {
  if (modules.empty()) {
    // nothing to be added
    return;
  }

  if (modules_.empty()) {
    modules_ = std::move(modules);
    return;
  }

  size_t modulesSize = modules_.size();
  size_t modulesSizeToBeAdded = modules.size();
  bool moduleNamesNotEmpty = !modulesByName_.empty();
  modules_.reserve(modulesSize + modulesSizeToBeAdded);
  std::move(modules.begin(), modules.end(), std::back_inserter(modules_));
  // if it's empty, will populate in getConfig
  if (moduleNamesNotEmpty) {
    auto index = modulesSize;
    // update module names
    for (; index < modules_.size(); index++) {
      std::string name = modules_[index]->getName();
      modulesByName_[name] = index;
    }
  }
}

std::vector<std::string> ModuleRegistry::moduleNames() {
  std::vector<std::string> names;
  for (size_t i = 0; i < modules_.size(); i++) {
    std::string name = modules_[i]->getName();
    modulesByName_[name] = i;
    names.push_back(std::move(name));
  }
  return names;
}

std::optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
  // Initialize modulesByName_
  if (modulesByName_.empty() && !modules_.empty()) {
    moduleNames();
  }

  auto it = modulesByName_.find(name);
  if (it == modulesByName_.end()) {
    throw std::runtime_error("unable to find module " + name);
  }
  size_t index = it->second;
  assert(index < modules_.size());
  NativeModule* module = modules_[index].get();
  std::unordered_map<std::string, size_t> methodNames;
  // syncMethodIds not used yet
  std::set<size_t> syncMethodIds;
  {
    std::vector<MethodDescriptor> methods = module->getMethods();

    for (int i = 0; i < methods.size(); i++) {
      methodNames[methods[i].name] = i;
      if (methods[i].type == MethodType::SYNC) {
        syncMethodIds.insert(i);
      }
    }
  }
  return ModuleConfig{.moduleName = name,
                      .moduleID = index,
                      .methods = methodNames,
                      .syncMethodIds = syncMethodIds,
                      .constants = module->getConstants()};
}

void ModuleRegistry::callNativeMethodSync(unsigned int moduleId, unsigned int methodId, std::vector<Dynamic>& params) {
  if (moduleId >= modules_.size()) {
    throw std::runtime_error("moduleId " + std::to_string(moduleId) + " out of range [0.." +
                             std::to_string(modules_.size()) + ")");
  }
  modules_[moduleId]->invokeSync(methodId, params);
}

void ModuleRegistry::callNativeMethodAsync(unsigned int moduleId, unsigned int methodId, std::vector<Dynamic>& params,
                                           unsigned int callbackId) {
  if (moduleId >= modules_.size()) {
    throw std::runtime_error("moduleId " + std::to_string(moduleId) + " out of range [0.." +
                             std::to_string(modules_.size()) + ")");
  }
  modules_[moduleId]->invokeAsync(methodId, params, callbackId);
}
}  // namespace jsiexecutor
